// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Simulate end to end streaming.
//
// Input:
// --source=
//   WebM used as the source of video and audio frames.
// --output=
//   File path to writing out the raw event log of the simulation session.
// --sim-id=
//   Unique simulation ID.
// --target-delay-ms=
//   Target playout delay to configure (integer number of milliseconds).
//   Optional; default is 400.
// --max-frame-rate=
//   The maximum frame rate allowed at any time during the Cast session.
//   Optional; default is 30.
// --source-frame-rate=
//   Overrides the playback rate; the source video will play faster/slower.
// --run-time=
//   In seconds, how long the Cast session runs for.
//   Optional; default is 180.
// --metrics-output=
//   File path to write PSNR and SSIM metrics between source frames and
//   decoded frames. Assumes all encoded frames are decoded.
// --yuv-output=
//   File path to write YUV decoded frames in YUV4MPEG2 format.
// --no-simulation
//   Do not run network simulation.
//
// Output:
// - Raw event log of the simulation session tagged with the unique test ID,
//   written out to the specified file path.

#include <stddef.h>
#include <stdint.h>

#include <utility>

#include "base/at_exit.h"
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/files/scoped_file.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/tick_clock.h"
#include "base/values.h"
#include "media/base/audio_bus.h"
#include "media/base/fake_single_thread_task_runner.h"
#include "media/base/media.h"
#include "media/base/video_frame.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_environment.h"
#include "media/cast/cast_receiver.h"
#include "media/cast/cast_sender.h"
#include "media/cast/logging/encoding_event_subscriber.h"
#include "media/cast/logging/log_serializer.h"
#include "media/cast/logging/logging_defines.h"
#include "media/cast/logging/proto/raw_events.pb.h"
#include "media/cast/logging/raw_event_subscriber_bundle.h"
#include "media/cast/logging/simple_event_subscriber.h"
#include "media/cast/net/cast_transport.h"
#include "media/cast/net/cast_transport_config.h"
#include "media/cast/net/cast_transport_defines.h"
#include "media/cast/net/cast_transport_impl.h"
#include "media/cast/test/fake_media_source.h"
#include "media/cast/test/loopback_transport.h"
#include "media/cast/test/proto/network_simulation_model.pb.h"
#include "media/cast/test/skewed_tick_clock.h"
#include "media/cast/test/utility/audio_utility.h"
#include "media/cast/test/utility/default_config.h"
#include "media/cast/test/utility/test_util.h"
#include "media/cast/test/utility/udp_proxy.h"
#include "media/cast/test/utility/video_utility.h"

using media::cast::proto::IPPModel;
using media::cast::proto::NetworkSimulationModel;
using media::cast::proto::NetworkSimulationModelType;

namespace media {
namespace cast {
    namespace {
        const char kLibDir[] = "lib-dir";
        const char kModelPath[] = "model";
        const char kMetricsOutputPath[] = "metrics-output";
        const char kOutputPath[] = "output";
        const char kMaxFrameRate[] = "max-frame-rate";
        const char kNoSimulation[] = "no-simulation";
        const char kRunTime[] = "run-time";
        const char kSimulationId[] = "sim-id";
        const char kSourcePath[] = "source";
        const char kSourceFrameRate[] = "source-frame-rate";
        const char kTargetDelay[] = "target-delay-ms";
        const char kYuvOutputPath[] = "yuv-output";

        int GetIntegerSwitchValue(const char* switch_name, int default_value)
        {
            const std::string as_str = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switch_name);
            if (as_str.empty())
                return default_value;
            int as_int;
            CHECK(base::StringToInt(as_str, &as_int));
            CHECK_GT(as_int, 0);
            return as_int;
        }

        void LogAudioOperationalStatus(OperationalStatus status)
        {
            LOG(INFO) << "Audio status: " << status;
        }

        void LogVideoOperationalStatus(OperationalStatus status)
        {
            LOG(INFO) << "Video status: " << status;
        }

        struct PacketProxy {
            PacketProxy()
                : receiver(NULL)
            {
            }
            void ReceivePacket(std::unique_ptr<Packet> packet)
            {
                if (receiver)
                    receiver->ReceivePacket(std::move(packet));
            }
            CastReceiver* receiver;
        };

        class TransportClient : public CastTransport::Client {
        public:
            TransportClient(LogEventDispatcher* log_event_dispatcher,
                PacketProxy* packet_proxy)
                : log_event_dispatcher_(log_event_dispatcher)
                , packet_proxy_(packet_proxy)
            {
            }

            void OnStatusChanged(CastTransportStatus status) final
            {
                LOG(INFO) << "Cast transport status: " << status;
            };
            void OnLoggingEventsReceived(
                std::unique_ptr<std::vector<FrameEvent>> frame_events,
                std::unique_ptr<std::vector<PacketEvent>> packet_events) final
            {
                DCHECK(log_event_dispatcher_);
                log_event_dispatcher_->DispatchBatchOfEvents(std::move(frame_events),
                    std::move(packet_events));
            };
            void ProcessRtpPacket(std::unique_ptr<Packet> packet) final
            {
                if (packet_proxy_)
                    packet_proxy_->ReceivePacket(std::move(packet));
            };

        private:
            LogEventDispatcher* const log_event_dispatcher_; // Not owned by this class.
            PacketProxy* const packet_proxy_; // Not owned by this class.

            DISALLOW_COPY_AND_ASSIGN(TransportClient);
        };

        // Maintains a queue of encoded video frames.
        // This works by tracking FRAME_CAPTURE_END and FRAME_ENCODED events.
        // If a video frame is detected to be encoded it transfers a frame
        // from FakeMediaSource to its internal queue. Otherwise it drops a
        // frame from FakeMediaSource.
        class EncodedVideoFrameTracker : public RawEventSubscriber {
        public:
            EncodedVideoFrameTracker(FakeMediaSource* media_source)
                : media_source_(media_source)
                , last_frame_event_type_(UNKNOWN)
            {
            }
            ~EncodedVideoFrameTracker() final { }

            // RawEventSubscriber implementations.
            void OnReceiveFrameEvent(const FrameEvent& frame_event) final
            {
                // This method only cares about video FRAME_CAPTURE_END and
                // FRAME_ENCODED events.
                if (frame_event.media_type != VIDEO_EVENT) {
                    return;
                }
                if (frame_event.type != FRAME_CAPTURE_END && frame_event.type != FRAME_ENCODED) {
                    return;
                }
                // If there are two consecutive FRAME_CAPTURE_END events that means
                // a frame is dropped.
                if (last_frame_event_type_ == FRAME_CAPTURE_END && frame_event.type == FRAME_CAPTURE_END) {
                    media_source_->PopOldestInsertedVideoFrame();
                }
                if (frame_event.type == FRAME_ENCODED) {
                    video_frames_.push(media_source_->PopOldestInsertedVideoFrame());
                }
                last_frame_event_type_ = frame_event.type;
            }

            void OnReceivePacketEvent(const PacketEvent& packet_event) final
            {
                // Don't care.
            }

            scoped_refptr<media::VideoFrame> PopOldestEncodedFrame()
            {
                CHECK(!video_frames_.empty());
                scoped_refptr<media::VideoFrame> video_frame = video_frames_.front();
                video_frames_.pop();
                return video_frame;
            }

        private:
            FakeMediaSource* media_source_;
            CastLoggingEvent last_frame_event_type_;
            std::queue<scoped_refptr<media::VideoFrame>> video_frames_;

            DISALLOW_COPY_AND_ASSIGN(EncodedVideoFrameTracker);
        };

        // Appends a YUV frame in I420 format to the file located at |path|.
        void AppendYuvToFile(const base::FilePath& path,
            scoped_refptr<media::VideoFrame> frame)
        {
            // Write YUV420 format to file.
            std::string header;
            base::StringAppendF(
                &header, "FRAME W%d H%d\n",
                frame->coded_size().width(),
                frame->coded_size().height());
            AppendToFile(path, header.data(), header.size());
            AppendToFile(path,
                reinterpret_cast<char*>(frame->data(media::VideoFrame::kYPlane)),
                frame->stride(media::VideoFrame::kYPlane) * frame->rows(media::VideoFrame::kYPlane));
            AppendToFile(path,
                reinterpret_cast<char*>(frame->data(media::VideoFrame::kUPlane)),
                frame->stride(media::VideoFrame::kUPlane) * frame->rows(media::VideoFrame::kUPlane));
            AppendToFile(path,
                reinterpret_cast<char*>(frame->data(media::VideoFrame::kVPlane)),
                frame->stride(media::VideoFrame::kVPlane) * frame->rows(media::VideoFrame::kVPlane));
        }

        // A container to save output of GotVideoFrame() for computation based
        // on output frames.
        struct GotVideoFrameOutput {
            GotVideoFrameOutput()
                : counter(0)
            {
            }
            int counter;
            std::vector<double> psnr;
            std::vector<double> ssim;
        };

        void GotVideoFrame(
            GotVideoFrameOutput* metrics_output,
            const base::FilePath& yuv_output,
            EncodedVideoFrameTracker* video_frame_tracker,
            CastReceiver* cast_receiver,
            const scoped_refptr<media::VideoFrame>& video_frame,
            const base::TimeTicks& render_time,
            bool continuous)
        {
            ++metrics_output->counter;
            cast_receiver->RequestDecodedVideoFrame(
                base::Bind(&GotVideoFrame, metrics_output, yuv_output,
                    video_frame_tracker, cast_receiver));

            // If |video_frame_tracker| is available that means we're computing
            // quality metrices.
            if (video_frame_tracker) {
                scoped_refptr<media::VideoFrame> src_frame = video_frame_tracker->PopOldestEncodedFrame();
                metrics_output->psnr.push_back(I420PSNR(src_frame, video_frame));
                metrics_output->ssim.push_back(I420SSIM(src_frame, video_frame));
            }

            if (!yuv_output.empty()) {
                AppendYuvToFile(yuv_output, video_frame);
            }
        }

        void GotAudioFrame(int* counter,
            CastReceiver* cast_receiver,
            std::unique_ptr<AudioBus> audio_bus,
            const base::TimeTicks& playout_time,
            bool is_continuous)
        {
            ++*counter;
            cast_receiver->RequestDecodedAudioFrame(
                base::Bind(&GotAudioFrame, counter, cast_receiver));
        }

        // Serialize |frame_events| and |packet_events| and append to the file
        // located at |output_path|.
        void AppendLogToFile(media::cast::proto::LogMetadata* metadata,
            const media::cast::FrameEventList& frame_events,
            const media::cast::PacketEventList& packet_events,
            const base::FilePath& output_path)
        {
            media::cast::proto::GeneralDescription* gen_desc = metadata->mutable_general_description();
            gen_desc->set_product("Cast Simulator");
            gen_desc->set_product_version("0.1");

            std::unique_ptr<char[]> serialized_log(
                new char[media::cast::kMaxSerializedBytes]);
            int output_bytes;
            bool success = media::cast::SerializeEvents(*metadata,
                frame_events,
                packet_events,
                true,
                media::cast::kMaxSerializedBytes,
                serialized_log.get(),
                &output_bytes);

            if (!success) {
                LOG(ERROR) << "Failed to serialize log.";
                return;
            }

            if (!AppendToFile(output_path, serialized_log.get(), output_bytes)) {
                LOG(ERROR) << "Failed to append to log.";
            }
        }

        // Run simulation once.
        //
        // |log_output_path| is the path to write serialized log.
        // |extra_data| is extra tagging information to write to log.
        void RunSimulation(const base::FilePath& source_path,
            const base::FilePath& log_output_path,
            const base::FilePath& metrics_output_path,
            const base::FilePath& yuv_output_path,
            const std::string& extra_data,
            const NetworkSimulationModel& model)
        {
            // Fake clock. Make sure start time is non zero.
            base::SimpleTestTickClock testing_clock;
            testing_clock.Advance(base::TimeDelta::FromSeconds(1));

            // Task runner.
            scoped_refptr<FakeSingleThreadTaskRunner> task_runner = new FakeSingleThreadTaskRunner(&testing_clock);
            base::ThreadTaskRunnerHandle task_runner_handle(task_runner);

            // CastEnvironments.
            scoped_refptr<CastEnvironment> sender_env = new CastEnvironment(std::unique_ptr<base::TickClock>(
                                                                                new test::SkewedTickClock(&testing_clock)),
                task_runner, task_runner, task_runner);
            scoped_refptr<CastEnvironment> receiver_env = new CastEnvironment(std::unique_ptr<base::TickClock>(
                                                                                  new test::SkewedTickClock(&testing_clock)),
                task_runner, task_runner, task_runner);

            // Event subscriber. Store at most 1 hour of events.
            EncodingEventSubscriber audio_event_subscriber(AUDIO_EVENT,
                100 * 60 * 60);
            EncodingEventSubscriber video_event_subscriber(VIDEO_EVENT,
                30 * 60 * 60);
            sender_env->logger()->Subscribe(&audio_event_subscriber);
            sender_env->logger()->Subscribe(&video_event_subscriber);

            // Audio sender config.
            FrameSenderConfig audio_sender_config = GetDefaultAudioSenderConfig();
            audio_sender_config.min_playout_delay = audio_sender_config.max_playout_delay = base::TimeDelta::FromMilliseconds(
                GetIntegerSwitchValue(kTargetDelay, 400));

            // Audio receiver config.
            FrameReceiverConfig audio_receiver_config = GetDefaultAudioReceiverConfig();
            audio_receiver_config.rtp_max_delay_ms = audio_sender_config.max_playout_delay.InMilliseconds();

            // Video sender config.
            FrameSenderConfig video_sender_config = GetDefaultVideoSenderConfig();
            video_sender_config.max_bitrate = 2500000;
            video_sender_config.min_bitrate = 2000000;
            video_sender_config.start_bitrate = 2000000;
            video_sender_config.min_playout_delay = video_sender_config.max_playout_delay = audio_sender_config.max_playout_delay;
            video_sender_config.max_frame_rate = GetIntegerSwitchValue(kMaxFrameRate, 30);

            // Video receiver config.
            FrameReceiverConfig video_receiver_config = GetDefaultVideoReceiverConfig();
            video_receiver_config.rtp_max_delay_ms = video_sender_config.max_playout_delay.InMilliseconds();

            // Loopback transport. Owned by CastTransport.
            LoopBackTransport* receiver_to_sender = new LoopBackTransport(receiver_env);
            LoopBackTransport* sender_to_receiver = new LoopBackTransport(sender_env);

            PacketProxy packet_proxy;

            // Cast receiver.
            std::unique_ptr<CastTransport> transport_receiver(new CastTransportImpl(
                &testing_clock, base::TimeDelta::FromSeconds(1),
                base::MakeUnique<TransportClient>(receiver_env->logger(), &packet_proxy),
                base::WrapUnique(receiver_to_sender), task_runner));
            std::unique_ptr<CastReceiver> cast_receiver(
                CastReceiver::Create(receiver_env, audio_receiver_config,
                    video_receiver_config, transport_receiver.get()));

            packet_proxy.receiver = cast_receiver.get();

            // Cast sender and transport sender.
            std::unique_ptr<CastTransport> transport_sender(new CastTransportImpl(
                &testing_clock, base::TimeDelta::FromSeconds(1),
                base::MakeUnique<TransportClient>(sender_env->logger(), nullptr),
                base::WrapUnique(sender_to_receiver), task_runner));
            std::unique_ptr<CastSender> cast_sender(
                CastSender::Create(sender_env, transport_sender.get()));

            // Initialize network simulation model.
            const bool use_network_simulation = model.type() == media::cast::proto::INTERRUPTED_POISSON_PROCESS;
            std::unique_ptr<test::InterruptedPoissonProcess> ipp;
            if (use_network_simulation) {
                LOG(INFO) << "Running Poisson based network simulation.";
                const IPPModel& ipp_model = model.ipp();
                std::vector<double> average_rates(ipp_model.average_rate_size());
                std::copy(ipp_model.average_rate().begin(),
                    ipp_model.average_rate().end(),
                    average_rates.begin());
                ipp.reset(new test::InterruptedPoissonProcess(
                    average_rates,
                    ipp_model.coef_burstiness(), ipp_model.coef_variance(), 0));
                receiver_to_sender->Initialize(ipp->NewBuffer(128 * 1024),
                    transport_sender->PacketReceiverForTesting(),
                    task_runner, &testing_clock);
                sender_to_receiver->Initialize(
                    ipp->NewBuffer(128 * 1024),
                    transport_receiver->PacketReceiverForTesting(), task_runner,
                    &testing_clock);
            } else {
                LOG(INFO) << "No network simulation.";
                receiver_to_sender->Initialize(std::unique_ptr<test::PacketPipe>(),
                    transport_sender->PacketReceiverForTesting(),
                    task_runner, &testing_clock);
                sender_to_receiver->Initialize(
                    std::unique_ptr<test::PacketPipe>(),
                    transport_receiver->PacketReceiverForTesting(), task_runner,
                    &testing_clock);
            }

            // Initialize a fake media source and a tracker to encoded video frames.
            const bool quality_test = !metrics_output_path.empty();
            FakeMediaSource media_source(task_runner,
                &testing_clock,
                audio_sender_config,
                video_sender_config,
                quality_test);
            std::unique_ptr<EncodedVideoFrameTracker> video_frame_tracker;
            if (quality_test) {
                video_frame_tracker.reset(new EncodedVideoFrameTracker(&media_source));
                sender_env->logger()->Subscribe(video_frame_tracker.get());
            }

            // Quality metrics computed for each frame decoded.
            GotVideoFrameOutput metrics_output;

            // Start receiver.
            int audio_frame_count = 0;
            cast_receiver->RequestDecodedVideoFrame(
                base::Bind(&GotVideoFrame, &metrics_output, yuv_output_path,
                    video_frame_tracker.get(), cast_receiver.get()));
            cast_receiver->RequestDecodedAudioFrame(
                base::Bind(&GotAudioFrame, &audio_frame_count, cast_receiver.get()));

            // Initializing audio and video senders.
            cast_sender->InitializeAudio(audio_sender_config,
                base::Bind(&LogAudioOperationalStatus));
            cast_sender->InitializeVideo(media_source.get_video_config(),
                base::Bind(&LogVideoOperationalStatus),
                CreateDefaultVideoEncodeAcceleratorCallback(),
                CreateDefaultVideoEncodeMemoryCallback());
            task_runner->RunTasks();

            // Truncate YUV files to prepare for writing.
            if (!yuv_output_path.empty()) {
                base::ScopedFILE file(base::OpenFile(yuv_output_path, "wb"));
                if (!file.get()) {
                    LOG(ERROR) << "Cannot save YUV output to file.";
                    return;
                }
                LOG(INFO) << "Writing YUV output to file: " << yuv_output_path.value();

                // Write YUV4MPEG2 header.
                const std::string header("YUV4MPEG2 W1280 H720 F30000:1001 Ip A1:1 C420\n");
                AppendToFile(yuv_output_path, header.data(), header.size());
            }

            // Start sending.
            if (!source_path.empty()) {
                // 0 means using the FPS from the file.
                media_source.SetSourceFile(source_path,
                    GetIntegerSwitchValue(kSourceFrameRate, 0));
            }
            media_source.Start(cast_sender->audio_frame_input(),
                cast_sender->video_frame_input());

            // By default runs simulation for 3 minutes or the desired duration
            // by using --run-time= flag.
            base::TimeDelta elapsed_time;
            const base::TimeDelta desired_run_time = base::TimeDelta::FromSeconds(GetIntegerSwitchValue(kRunTime, 180));
            while (elapsed_time < desired_run_time) {
                // Each step is 100us.
                base::TimeDelta step = base::TimeDelta::FromMicroseconds(100);
                task_runner->Sleep(step);
                elapsed_time += step;
            }

            // Unsubscribe from logging events.
            sender_env->logger()->Unsubscribe(&audio_event_subscriber);
            sender_env->logger()->Unsubscribe(&video_event_subscriber);
            if (quality_test)
                sender_env->logger()->Unsubscribe(video_frame_tracker.get());

            // Get event logs for audio and video.
            media::cast::proto::LogMetadata audio_metadata, video_metadata;
            media::cast::FrameEventList audio_frame_events, video_frame_events;
            media::cast::PacketEventList audio_packet_events, video_packet_events;
            audio_metadata.set_extra_data(extra_data);
            video_metadata.set_extra_data(extra_data);
            audio_event_subscriber.GetEventsAndReset(
                &audio_metadata, &audio_frame_events, &audio_packet_events);
            video_event_subscriber.GetEventsAndReset(
                &video_metadata, &video_frame_events, &video_packet_events);

            // Print simulation results.

            // Compute and print statistics for video:
            //
            // * Total video frames captured.
            // * Total video frames encoded.
            // * Total video frames dropped.
            // * Total video frames received late.
            // * Average target bitrate.
            // * Average encoded bitrate.
            int total_video_frames = 0;
            int encoded_video_frames = 0;
            int dropped_video_frames = 0;
            int late_video_frames = 0;
            int64_t total_delay_of_late_frames_ms = 0;
            int64_t encoded_size = 0;
            int64_t target_bitrate = 0;
            for (size_t i = 0; i < video_frame_events.size(); ++i) {
                const media::cast::proto::AggregatedFrameEvent& event = *video_frame_events[i];
                ++total_video_frames;
                if (event.has_encoded_frame_size()) {
                    ++encoded_video_frames;
                    encoded_size += event.encoded_frame_size();
                    target_bitrate += event.target_bitrate();
                } else {
                    ++dropped_video_frames;
                }
                if (event.has_delay_millis() && event.delay_millis() < 0) {
                    ++late_video_frames;
                    total_delay_of_late_frames_ms += -event.delay_millis();
                }
            }

            // Subtract fraction of dropped frames from |elapsed_time| before estimating
            // the average encoded bitrate.
            const base::TimeDelta elapsed_time_undropped = total_video_frames <= 0 ? base::TimeDelta() : (elapsed_time * (total_video_frames - dropped_video_frames) / total_video_frames);
            const double avg_encoded_bitrate = elapsed_time_undropped <= base::TimeDelta() ? 0 : 8.0 * encoded_size / elapsed_time_undropped.InSecondsF() / 1000;
            double avg_target_bitrate = !encoded_video_frames ? 0 : target_bitrate / encoded_video_frames / 1000;

            LOG(INFO) << "Configured target playout delay (ms): "
                      << video_receiver_config.rtp_max_delay_ms;
            LOG(INFO) << "Audio frame count: " << audio_frame_count;
            LOG(INFO) << "Inserted video frames: " << total_video_frames;
            LOG(INFO) << "Decoded video frames: " << metrics_output.counter;
            LOG(INFO) << "Dropped video frames: " << dropped_video_frames;
            LOG(INFO) << "Late video frames: " << late_video_frames
                      << " (average lateness: "
                      << (late_video_frames > 0 ? static_cast<double>(total_delay_of_late_frames_ms) / late_video_frames : 0)
                      << " ms)";
            LOG(INFO) << "Average encoded bitrate (kbps): " << avg_encoded_bitrate;
            LOG(INFO) << "Average target bitrate (kbps): " << avg_target_bitrate;
            LOG(INFO) << "Writing log: " << log_output_path.value();

            // Truncate file and then write serialized log.
            {
                base::ScopedFILE file(base::OpenFile(log_output_path, "wb"));
                if (!file.get()) {
                    LOG(INFO) << "Cannot write to log.";
                    return;
                }
            }
            AppendLogToFile(&video_metadata, video_frame_events, video_packet_events,
                log_output_path);
            AppendLogToFile(&audio_metadata, audio_frame_events, audio_packet_events,
                log_output_path);

            // Write quality metrics.
            if (quality_test) {
                LOG(INFO) << "Writing quality metrics: " << metrics_output_path.value();
                std::string line;
                for (size_t i = 0; i < metrics_output.psnr.size() && i < metrics_output.ssim.size(); ++i) {
                    base::StringAppendF(&line, "%f %f\n", metrics_output.psnr[i],
                        metrics_output.ssim[i]);
                }
                WriteFile(metrics_output_path, line.data(), line.length());
            }
        }

        NetworkSimulationModel DefaultModel()
        {
            NetworkSimulationModel model;
            model.set_type(cast::proto::INTERRUPTED_POISSON_PROCESS);
            IPPModel* ipp = model.mutable_ipp();
            ipp->set_coef_burstiness(0.609);
            ipp->set_coef_variance(4.1);

            ipp->add_average_rate(0.609);
            ipp->add_average_rate(0.495);
            ipp->add_average_rate(0.561);
            ipp->add_average_rate(0.458);
            ipp->add_average_rate(0.538);
            ipp->add_average_rate(0.513);
            ipp->add_average_rate(0.585);
            ipp->add_average_rate(0.592);
            ipp->add_average_rate(0.658);
            ipp->add_average_rate(0.556);
            ipp->add_average_rate(0.371);
            ipp->add_average_rate(0.595);
            ipp->add_average_rate(0.490);
            ipp->add_average_rate(0.980);
            ipp->add_average_rate(0.781);
            ipp->add_average_rate(0.463);

            return model;
        }

        bool IsModelValid(const NetworkSimulationModel& model)
        {
            if (!model.has_type())
                return false;
            NetworkSimulationModelType type = model.type();
            if (type == media::cast::proto::INTERRUPTED_POISSON_PROCESS) {
                if (!model.has_ipp())
                    return false;
                const IPPModel& ipp = model.ipp();
                if (ipp.coef_burstiness() <= 0.0 || ipp.coef_variance() <= 0.0)
                    return false;
                if (ipp.average_rate_size() == 0)
                    return false;
                for (int i = 0; i < ipp.average_rate_size(); i++) {
                    if (ipp.average_rate(i) <= 0.0)
                        return false;
                }
            }

            return true;
        }

        NetworkSimulationModel LoadModel(const base::FilePath& model_path)
        {
            if (base::CommandLine::ForCurrentProcess()->HasSwitch(kNoSimulation)) {
                NetworkSimulationModel model;
                model.set_type(media::cast::proto::NO_SIMULATION);
                return model;
            }
            if (model_path.empty()) {
                LOG(ERROR) << "Model path not set; Using default model.";
                return DefaultModel();
            }
            std::string model_str;
            if (!base::ReadFileToString(model_path, &model_str)) {
                LOG(ERROR) << "Failed to read model file.";
                return DefaultModel();
            }

            NetworkSimulationModel model;
            if (!model.ParseFromString(model_str)) {
                LOG(ERROR) << "Failed to parse model.";
                return DefaultModel();
            }
            if (!IsModelValid(model)) {
                LOG(ERROR) << "Invalid model.";
                return DefaultModel();
            }

            return model;
        }

    } // namespace
} // namespace cast
} // namespace media

int main(int argc, char** argv)
{
    base::AtExitManager at_exit;
    base::CommandLine::Init(argc, argv);
    InitLogging(logging::LoggingSettings());

    const base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
    base::FilePath media_path = cmd->GetSwitchValuePath(media::cast::kLibDir);
    if (media_path.empty()) {
        if (!PathService::Get(base::DIR_MODULE, &media_path)) {
            LOG(ERROR) << "Failed to load FFmpeg.";
            return 1;
        }
    }

    media::InitializeMediaLibrary();

    base::FilePath source_path = cmd->GetSwitchValuePath(
        media::cast::kSourcePath);
    base::FilePath log_output_path = cmd->GetSwitchValuePath(
        media::cast::kOutputPath);
    if (log_output_path.empty()) {
        base::GetTempDir(&log_output_path);
        log_output_path = log_output_path.AppendASCII("sim-events.gz");
    }
    base::FilePath metrics_output_path = cmd->GetSwitchValuePath(
        media::cast::kMetricsOutputPath);
    base::FilePath yuv_output_path = cmd->GetSwitchValuePath(
        media::cast::kYuvOutputPath);
    std::string sim_id = cmd->GetSwitchValueASCII(media::cast::kSimulationId);

    NetworkSimulationModel model = media::cast::LoadModel(
        cmd->GetSwitchValuePath(media::cast::kModelPath));

    base::DictionaryValue values;
    values.SetBoolean("sim", true);
    values.SetString("sim-id", sim_id);

    std::string extra_data;
    base::JSONWriter::Write(values, &extra_data);

    // Run.
    media::cast::RunSimulation(source_path, log_output_path, metrics_output_path,
        yuv_output_path, extra_data, model);
    return 0;
}
